﻿using System;
using Beaker.OpenCube.Nbt;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Beaker.OpenCube.Specs.Nbt
{
	[TestClass]
	public class NbtMappingSpecs
	{
		[TestMethod]
		public void MappingOfByte()
		{
			// arrange
			NbtDictionary dict = new NbtDictionary();
			dict["FooBar"] = (byte)0x85;

			// act
			var deserializer = new NbtDeserializer<ByteTest>();
			ByteTest test = deserializer.Deserialize( dict );

			// assert
			test.Should().NotBeNull();
			test.FooBar.Should().Be( (byte)0x85 );
		}

		[TestMethod]
		public void MappingOfSByte()
		{
			// arrange
			NbtDictionary dict = new NbtDictionary();
			dict["FooBar"] = (sbyte)0x25;

			// act
			var deserializer = new NbtDeserializer<SByteTest>();
			SByteTest test = deserializer.Deserialize( dict );

			// assert
			test.Should().NotBeNull();
			test.FooBar.Should().Be( (sbyte)0x25 );
		}

		[TestMethod]
		public void MappingOfShort()
		{
			// arrange
			NbtDictionary dict = new NbtDictionary();
			dict["FooBar"] = (short)0x6e25;

			// act
			var deserializer = new NbtDeserializer<ShortTest>();
			ShortTest test = deserializer.Deserialize( dict );

			// assert
			test.Should().NotBeNull();
			test.FooBar.Should().Be( (short)0x6e25 );
		}

		[TestMethod]
		public void MappingOfUShort()
		{
			// arrange
			NbtDictionary dict = new NbtDictionary();
			dict["FooBar"] = (ushort)0x8e25;

			// act
			var deserializer = new NbtDeserializer<UShortTest>();
			UShortTest test = deserializer.Deserialize( dict );

			// assert
			test.Should().NotBeNull();
			test.FooBar.Should().Be( (ushort)0x8e25 );
		}

		[TestMethod]
		public void MappingOfInt()
		{
			// arrange
			NbtDictionary dict = new NbtDictionary();
			dict["FooBar"] = (int)0x6e2573ea;

			// act
			var deserializer = new NbtDeserializer<IntTest>();
			IntTest test = deserializer.Deserialize( dict );

			// assert
			test.Should().NotBeNull();
			test.FooBar.Should().Be( (int)0x6e2573ea );
		}

		[TestMethod]
		public void MappingOfUInt()
		{
			// arrange
			NbtDictionary dict = new NbtDictionary();
			dict["FooBar"] = (uint)0x8e25ea73;

			// act
			var deserializer = new NbtDeserializer<UIntTest>();
			UIntTest test = deserializer.Deserialize( dict );

			// assert
			test.Should().NotBeNull();
			test.FooBar.Should().Be( (uint)0x8e25ea73 );
		}

		[TestMethod]
		public void MappingOfLong()
		{
			// arrange
			NbtDictionary dict = new NbtDictionary();
			dict["FooBar"] = (long)0x6e25ebb1a8401bef;

			// act
			var deserializer = new NbtDeserializer<LongTest>();
			LongTest test = deserializer.Deserialize( dict );

			// assert
			test.Should().NotBeNull();
			test.FooBar.Should().Be( (long)0x6e25ebb1a8401bef );
		}

		[TestMethod]
		public void MappingOfULong()
		{
			// arrange
			NbtDictionary dict = new NbtDictionary();
			dict["FooBar"] = (ulong)0x8e25ebb1a8401bef;

			// act
			var deserializer = new NbtDeserializer<ULongTest>();
			ULongTest test = deserializer.Deserialize( dict );

			// assert
			test.Should().NotBeNull();
			test.FooBar.Should().Be( (ulong)0x8e25ebb1a8401bef );
		}

		[TestMethod]
		public void MappingOfCompound()
		{
			// arrange
			NbtDictionary dict = new NbtDictionary();
			NbtDictionary innerDict = new NbtDictionary();
			dict["InnerCompound"] = innerDict;
			innerDict["FooBar"] = (byte)0x3a;

			// act
			var deserializer = new NbtDeserializer<OuterCompoundTest>();
			OuterCompoundTest test = deserializer.Deserialize( dict );

			// assert
			test.Should().NotBeNull();
			test.InnerCompound.Should().NotBeNull();
			test.InnerCompound.FooBar.Should().Be( (byte)0x3a );
		}

		[TestMethod]
		public void MappingOfSingleDimensionalWritableByteArray()
		{
			// arrange
			NbtDictionary dict = new NbtDictionary();
			dict["Values"] = new sbyte[] { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, -0x80 };

			// act
			var deserializer = new NbtDeserializer<WritableByteArrayTest>();
			WritableByteArrayTest test = deserializer.Deserialize( dict );

			// assert
			test.Should().NotBeNull();
			test.Values.Should().Equal( new sbyte[] { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, -0x80 } );
		}

		[TestMethod]
		public void MappingOfSingleDimensionalReadOnlyByteArray()
		{
			// arrange
			NbtDictionary dict = new NbtDictionary();
			dict["Values"] = new sbyte[] { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, -0x80 };

			// act
			var deserializer = new NbtDeserializer<ReadOnlyByteArrayTest>();
			ReadOnlyByteArrayTest test = deserializer.Deserialize( dict );

			// assert
			test.Should().NotBeNull();
			test.Values.Should().Equal( new sbyte[] { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, -0x80 } );
		}

		[TestMethod]
		public void MappingOfMultiDimensionalReadOnlyByteArray()
		{
			// arrange
			NbtDictionary dict = new NbtDictionary();
			dict["Values"] = new sbyte[] { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, -0x80 };

			// act
			var deserializer = new NbtDeserializer<ReadOnlyMultiByteArrayTest>();
			ReadOnlyMultiByteArrayTest test = deserializer.Deserialize( dict );

			// assert
			test.Should().NotBeNull();
			test.Values[0, 0].Should().Be( 0x01 );
			test.Values[0, 1].Should().Be( 0x02 );
			test.Values[0, 2].Should().Be( 0x04 );
			test.Values[0, 3].Should().Be( 0x08 );
			test.Values[1, 0].Should().Be( 0x10 );
			test.Values[1, 1].Should().Be( 0x20 );
			test.Values[1, 2].Should().Be( 0x40 );
			test.Values[1, 3].Should().Be( -0x80 );
		}

		[TestMethod]
		public void MappingOfMultiDimensionalReadOnlyIntArray()
		{
			// arrange
			NbtDictionary dict = new NbtDictionary();
			dict["Values"] = new int[] { 72, 865, 812, 65821, 5, 931, 97683, -8234 };

			// act
			var deserializer = new NbtDeserializer<ReadOnlyMultiIntArrayTest>();
			ReadOnlyMultiIntArrayTest test = deserializer.Deserialize( dict );

			// assert
			test.Should().NotBeNull();
			test.Values[0, 0].Should().Be( 72 );
			test.Values[0, 1].Should().Be( 865 );
			test.Values[0, 2].Should().Be( 812 );
			test.Values[0, 3].Should().Be( 65821 );
			test.Values[1, 0].Should().Be( 5 );
			test.Values[1, 1].Should().Be( 931 );
			test.Values[1, 2].Should().Be( 97683 );
			test.Values[1, 3].Should().Be( -8234 );
		}

		[TestMethod]
		public void MappingOfMultiDimensionalReadOnlyEnumArray()
		{
			// arrange
			NbtDictionary dict = new NbtDictionary();
			dict["Values"] = new sbyte[] { -1, 0, 1, 2, 3, 4, 5, 6 };

			// act
			var deserializer = new NbtDeserializer<ReadOnlyMultiEnumArrayTest>();
			ReadOnlyMultiEnumArrayTest test = deserializer.Deserialize( dict );

			// assert
			test.Should().NotBeNull();
			test.Values[0, 0, 0].Should().Be( EnumTest.Negative );
			test.Values[0, 0, 1].Should().Be( EnumTest.Zero );
			test.Values[0, 1, 0].Should().Be( EnumTest.One );
			test.Values[0, 1, 1].Should().Be( EnumTest.Two );
			test.Values[1, 0, 0].Should().Be( EnumTest.Three );
			test.Values[1, 0, 1].Should().Be( EnumTest.Four );
			test.Values[1, 1, 0].Should().Be( EnumTest.Five );
			test.Values[1, 1, 1].Should().Be( EnumTest.Six );
		}

		[TestMethod]
		public void MappingToDifferentNamedValue()
		{
			// arrange
			NbtDictionary dict = new NbtDictionary();
			dict["NbtName"] = "Chocomelat";

			// act
			var deserializer = new NbtDeserializer<NamedTest>();
			NamedTest test = deserializer.Deserialize( dict );

			// assert
			test.Should().NotBeNull();
			test.LocalName.Should().Be( "Chocomelat" );
		}

		[TestMethod]
		public void MappingOfMissingOptionalShouldSkipProperty()
		{
			// arrange
			NbtDictionary dict = new NbtDictionary();
			dict["Required"] = "FooBar";

			// act
			var deserializer = new NbtDeserializer<OptionalTest>();
			OptionalTest test = deserializer.Deserialize( dict );

			// assert
			test.Should().NotBeNull();
			test.Required.Should().Be( "FooBar" );
			test.Optional.Should().BeNull();
		}

		[TestMethod]
		public void MappingOfMissingRequiredShouldThrow()
		{
			// arrange
			NbtDictionary dict = new NbtDictionary();
			dict["Optional"] = "FooBar";

			// act
			var deserializer = new NbtDeserializer<OptionalTest>();
			Action act = () => deserializer.Deserialize( dict );

			// assert
			act.ShouldThrow<NbtMappingException>();
		}

		#region Test Support Classes

		public class ByteTest
		{
			public byte FooBar { get; set; }
		}

		public class SByteTest
		{
			public sbyte FooBar { get; set; }
		}

		public class ShortTest
		{
			public short FooBar { get; set; }
		}

		public class UShortTest
		{
			public ushort FooBar { get; set; }
		}

		public class IntTest
		{
			public int FooBar { get; set; }
		}

		public class UIntTest
		{
			public uint FooBar { get; set; }
		}

		public class LongTest
		{
			public long FooBar { get; set; }
		}

		public class ULongTest
		{
			public ulong FooBar { get; set; }
		}

		public class WritableByteArrayTest
		{
			public sbyte[] Values { get; set; }
		}

		public class ReadOnlyByteArrayTest
		{
			private readonly sbyte[] _values = new sbyte[8];
			public sbyte[] Values { get { return _values; } }
		}

		public class ReadOnlyMultiByteArrayTest
		{
			private readonly sbyte[,] _values = new sbyte[2, 4];
			public sbyte[,] Values { get { return _values; } }
		}

		public class ReadOnlyMultiIntArrayTest
		{
			private readonly int[,] _values = new int[2, 4];
			public int[,] Values { get { return _values; } }
		}

		public class ReadOnlyMultiEnumArrayTest
		{
			private readonly EnumTest[, ,] _values = new EnumTest[2, 2, 2];
			public EnumTest[, ,] Values { get { return _values; } }
		}

		public enum EnumTest
			: sbyte
		{
			Negative = -1,
			Zero = 0,
			One = 1,
			Two = 2,
			Three = 3,
			Four = 4,
			Five = 5,
			Six = 6
		}

		public class OuterCompoundTest
		{
			public ByteTest InnerCompound { get; set; }
		}

		public class NamedTest
		{
			[NbtMapping( Name = "NbtName" )]
			public string LocalName { get; set; }
		}

		public class OptionalTest
		{
			public string Required { get; set; }

			[NbtMapping( IsOptional = true )]
			public string Optional { get; set; }
		}

		#endregion
	}
}
